package com.atguigu.gmall.product.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.model.product.*;
import com.atguigu.gmall.product.mapper.*;
import com.atguigu.gmall.product.service.ManageService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class ManageServiceImpl implements ManageService {
    // 引入mapper
    // 引入mapper报红线：springIOC容器没有识别到 mapper 把自动注入级别调为警告
    @Autowired
    private BaseCategory1Mapper baseCategory1Mapper;

    @Autowired
    private BaseCategory2Mapper baseCategory2Mapper;

    @Autowired
    private BaseCategory3Mapper baseCategory3Mapper;

    @Autowired
    private BaseAttrInfoMapper baseAttrInfoMapper;

    @Autowired
    private BaseAttrValueMapper baseAttrValueMapper;

    @Autowired
    private SpuInfoMapper spuInfoMapper;

    @Autowired
    private BaseSaleAttrMapper baseSaleAttrMapper;

    @Autowired
    private SpuImageMapper spuImageMapper;

    @Autowired
    private SpuSaleAttrValueMapper spuSaleAttrValueMapper;

    @Autowired
    private SpuSaleAttrMapper spuSaleAttrMapper;

    @Autowired
    private SkuInfoMapper skuInfoMapper;

    @Autowired
    private SkuImageMapper skuImageMapper;

    @Autowired
    private SkuAttrValueMapper skuAttrValueMapper;

    @Autowired
    private SkuSaleAttrValueMapper skuSaleAttrValueMapper;

    @Autowired
    private BaseCategoryViewMapper baseCategoryViewMapper;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private BaseTrademarkMapper baseTrademarkMapper;

    @Autowired
    private RabbitService rabbitService;

    @Override
    public List<BaseCategory1> getCategory1() {
        return baseCategory1Mapper.selectList(null);
    }

    @Override
    public List<BaseCategory2> getCategory2(Long category1Id) {
        // select * from base_category2 where category1_id = category1Id
        QueryWrapper queryWrapper = new QueryWrapper<BaseCategory2>();

        queryWrapper.eq("category1_id", category1Id);

        List<BaseCategory2> baseCategory2List = baseCategory2Mapper.selectList(queryWrapper);

        return baseCategory2List;

    }

    @Override
    public List<BaseCategory3> getCategory3(Long category2Id) {
        // select * from base_category2 where category2_id = category2Id
        QueryWrapper queryWrapper = new QueryWrapper<BaseCategory3>();

        queryWrapper.eq("category2_id", category2Id);

        return baseCategory3Mapper.selectList(queryWrapper);

    }

    @Override
    public List<BaseAttrInfo> getAttrInfoList(Long category1Id, Long category2Id, Long category3Id) {
        // 查询平台属性列表，需要编写一个复杂sql：多表关联查询
        // 多表关联查询：是为了能实现通过 分类id 查询平台属性，同时也可以 查询 平台属性值 的 功能
        return baseAttrInfoMapper.selectBaseAttrInfoList(category1Id, category2Id, category3Id);
    }

    @Override
    @Transactional // DML操作注意事务！！！
    public void saveAttrInfo(BaseAttrInfo baseAttrInfo) {
        // 该实现类 既有保存！又有修改！！！

        // 1 保存 baseAttrInfo 平台属性

        // 判断：属性值id不为空 表示为 修改操作
        if (baseAttrInfo.getId() != null) {
            baseAttrInfoMapper.updateById(baseAttrInfo);
        } else {
            baseAttrInfoMapper.insert(baseAttrInfo);
        }

        // 2 保存 baseAttrValue 平台属性值
        // 修改平台属性值的时候，系统无法确认用户想修改的数据是谁！（因为attrValueList里是集合 保存的数据有多个）
        // 通过另外一种方式修改平台属性：先删除，再新增
        // 根据平台属性id 将对应的属性值删除，再新增
        // delete from  base_attr_value where attr_id = base_attr_info.id
        baseAttrValueMapper.delete(new QueryWrapper<BaseAttrValue>().eq("attr_id", baseAttrInfo.getId()));

        // 2.1平台属性值 通过 attrValueList 集合获取（获取 平台属性 中的 属性值集合）
        List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();

        // 2.1 判断当前集合不能为空
        if (!CollectionUtils.isEmpty(attrValueList)) {

            // 2.2 循环遍历将数据添加到 平台属性值表 中！
            for (BaseAttrValue baseAttrValue : attrValueList) {

                // 细节处理 需要给 attrId 赋值，attrId == baseAttrInfo.id
                baseAttrValue.setAttrId(baseAttrInfo.getId());
                baseAttrValueMapper.insert(baseAttrValue);
            }
        }
    }

    @Override
    public List<BaseAttrValue> getAttrValueList(Long attrId) {
        // select * from  base_attr_value where attr_id = attrId
        QueryWrapper<BaseAttrValue> baseAttrValueQueryWrapper = new QueryWrapper<>();
        baseAttrValueQueryWrapper.eq("attr_id ", attrId);

        List<BaseAttrValue> baseAttrValueList = baseAttrValueMapper.selectList(baseAttrValueQueryWrapper);

        return baseAttrValueList;
    }


    @Override
    public BaseAttrInfo getBaseAttrInfo(Long attrId) {
        // 1 查询 平台属性
        // select * from base_attr_info where id = attrId
        BaseAttrInfo baseAttrInfo = baseAttrInfoMapper.selectById(attrId);

        if (baseAttrInfo != null) {
            // 2 获取平台属性值集合 放入 attrValueList 属性中
            baseAttrInfo.setAttrValueList(getAttrValueList(attrId));
        }
        return baseAttrInfo;
    }

    @Override
    public IPage<SpuInfo> getSupInfoPage(Page<SpuInfo> pageParam, SpuInfo spuInfo) {
        // 创建查询条件对象：根据 category3_id 查询
        QueryWrapper<SpuInfo> spuInfoQueryWrapper = new QueryWrapper<>();
        spuInfoQueryWrapper.eq("category3_id", spuInfo.getCategory3Id());

        // 设置一个排序规则
        spuInfoQueryWrapper.orderByDesc("id");

        return spuInfoMapper.selectPage(pageParam, spuInfoQueryWrapper);
    }

    @Override
    public List<BaseSaleAttr> getBaseSaleAttrList() {
        List<BaseSaleAttr> baseSaleAttrList = baseSaleAttrMapper.selectList(null);
        return baseSaleAttrList;
    }

    @Override
    @Transactional // !!!注意事务
    public void saveSpuInfo(SpuInfo spuInfo) {
        /**
         * 保存的数据
         *  spuInfo：spu名称、品牌、spu描述
         *  spuImage：保存图片信息
         *  SpuSaleAttr
         *  SpuSaleAttrValue
         */

        // 1 保存 spuInfo 中有：spu名称、品牌、spu描述
        spuInfoMapper.insert(spuInfo);

        // 2 spuImage：获取前台传递过来的图片信息
        List<SpuImage> spuImageList = spuInfo.getSpuImageList();

        if (!CollectionUtils.isEmpty(spuImageList)) {
            // 循环遍历
            for (SpuImage spuImage : spuImageList) {
                // 注意细节 spuId 页面没有传递，一开始为 null
                // 2.1 但是在 insert(spuInfo) 的时候 就产生了 spuId ，因为 SpuInfo实体类 中 id是自增 @TableId(type = IdType.AUTO)
                spuImage.setSpuId(spuInfo.getId());

                spuImageMapper.insert(spuImage);
            }
        }

        // 3 SpuSaleAttr 销售属性表
        List<SpuSaleAttr> spuSaleAttrList = spuInfo.getSpuSaleAttrList();

        if (!CollectionUtils.isEmpty(spuSaleAttrList)) {

            for (SpuSaleAttr spuSaleAttr : spuSaleAttrList) {
                // 设置 spuId
                spuSaleAttr.setSpuId(spuInfo.getId());

                spuSaleAttrMapper.insert(spuSaleAttr);

                // 4 spuSaleAttrValue 销售属性值表
                // 获取属性值集合
                List<SpuSaleAttrValue> spuSaleAttrValueList = spuSaleAttr.getSpuSaleAttrValueList();

                if (!CollectionUtils.isEmpty(spuSaleAttrValueList)) {

                    for (SpuSaleAttrValue spuSaleAttrValue : spuSaleAttrValueList) {
                        // 设置 spuId
                        spuSaleAttrValue.setSpuId(spuInfo.getId());

                        //bug: 页面没有传递 saleAttrName，但是，通过业务得知:
                        //spu_sale_attr_value.sale_attr_name = spu_sale_attr.sale_attr_name
                        spuSaleAttrValue.setSaleAttrName(spuSaleAttr.getSaleAttrName());

                        spuSaleAttrValueMapper.insert(spuSaleAttrValue);
                    }
                }
            }
        }
    }

    @Override
    public List<SpuImage> getSpuImageList(Long spuId) {
        QueryWrapper<SpuImage> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("spu_id", spuId);
        return spuImageMapper.selectList(queryWrapper);
    }


    @Override
    public List<SpuSaleAttr> getSpuSaleAttrList(Long spuId) {
        // 需要多表关联查询，需要使用xml配置sql
        List<SpuSaleAttr> spuSaleAttrList = spuSaleAttrMapper.selectSpuSaleAttrList(spuId);

        return spuSaleAttrList;
    }

    @Override
    @Transactional // !!!注意事务
    public void saveSkuInfo(SkuInfo skuInfo) {
        /**
         * 需要保存数据的表
         *  skuInfo 库存单元表
         *  skuImage 库存单元图片表
         *  skuAttrValue ：sku与平台属性值表（sku与平台属性值关系表）
         *  skuSaleAttrValue ：sku与销售属性值表（sku与销售属性值的关系表）
         */

        // 1 skuInfo 库存单元表
        skuInfoMapper.insert(skuInfo);

        // 2 skuImage 库存单元图片表
        List<SkuImage> skuImageList = skuInfo.getSkuImageList();
        if (!CollectionUtils.isEmpty(skuImageList)) {

            for (SkuImage skuImage : skuImageList) {
                // 2.1 为 skuId 赋值 因为页面没有传递
                skuImage.setSkuId(skuInfo.getId());

                skuImageMapper.insert(skuImage);
            }
        }

        // 3 skuAttrValue ：sku与平台属性值表（sku与平台属性值关系表）
        List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
        if (!CollectionUtils.isEmpty(skuAttrValueList)) {

            for (SkuAttrValue skuAttrValue : skuAttrValueList) {
                // 3.1 为 skuId 赋值 因为页面没有传递
                skuAttrValue.setSkuId(skuInfo.getId());

                skuAttrValueMapper.insert(skuAttrValue);
            }
        }

        // 4 skuSaleAttrValue ：sku与销售属性值表（记录sku与销售属性值的关系表）
        List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
        if (!CollectionUtils.isEmpty(skuSaleAttrValueList)) {

            for (SkuSaleAttrValue skuSaleAttrValue : skuSaleAttrValueList) {
                // 4.1 页面传递的值 我们只用 saleAttrValueId。但是还缺 skuId 和 spuId。
                skuSaleAttrValue.setSkuId(skuInfo.getId());
                skuSaleAttrValue.setSpuId(skuInfo.getSpuId());// 在保存 skuInfo 的时候 就插入了 spuId

                skuSaleAttrValueMapper.insert(skuSaleAttrValue);
            }
        }

        // 保存商品时候发送消息 - 商品上架：
        // 上架的时候我们在 service-list 中 SearchServiceImpl.upperGoods(Long skuId)通过 skuId 获取已经上架的商品
        // 故参数三发送的消息为 skuId 来确定商品上架
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_UPPER,skuInfo.getId());
    }

    @Override
    public IPage<SkuInfo> getSkuList(Page<SkuInfo> pageParam) {
        // 按id倒排序
        QueryWrapper<SkuInfo> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");

        // 调用mapper的分页查询方法
        return skuInfoMapper.selectPage(pageParam, wrapper);

    }

    @Override
    @Transactional
    public void onSale(Long skuId) {
        // 更改销售状态 is_sale = 1
        SkuInfo skuInfoUp = new SkuInfo();
        skuInfoUp.setId(skuId);
        skuInfoUp.setIsSale(1);
        // 动态更新 is_sale 不会修改其他字段
        skuInfoMapper.updateById(skuInfoUp);

        // 发送消息 - 商品上架
        // 上架的时候我们在 service-list 中 SearchServiceImpl.upperGoods(Long skuId)通过 skuId 获取已经上架的商品 故发送的消息为 skuId
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_UPPER,skuId);
    }


    @Override
    @Transactional
    public void cancelSale(Long skuId) {
        // 更改销售状态 is_sale = 0
        SkuInfo skuInfoUp = new SkuInfo();
        skuInfoUp.setId(skuId);
        skuInfoUp.setIsSale(0);
        // 动态更新 is_sale 不会修改其他字段
        skuInfoMapper.updateById(skuInfoUp);

        // 发送消息 - 商品下架 注意路由键为 MqConst.ROUTING_GOODS_LOWER 下架
        // 确定商品下架也是通过 skuId
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_LOWER,skuId);
    }

    @GmallCache(prifix = "sku:")
    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        //return getSkuInfoByRedisson(skuId);
        //return getSkuInfoByRedisSetLua(skuId);

        // @GmallCache 中 point.proceed() 表示 执行带有@GmallCache注解的方法体，并返回数据
        // 所以直接调用查询数据库的方法 缓存和分布式锁使用注解来完成！！！
        return getSkuInfoDB(skuId);
    }

    // 使用 Redisson 实现分布式锁
    private SkuInfo getSkuInfoByRedisson(Long skuId) {
        // 声明一个 SkuInfo 对象
        SkuInfo skuInfo = null;

        try {
            // 定义一个缓存的 key=sku:skuId:info
            String skuKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;

            // 获取缓存数据
            skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);

            if (skuInfo == null) {// 缓存中无数据：查 DB + 存缓存

                // 设置锁的key：lockKey = sku:skuId:lock
                String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;

                // 使用 RedissonClient 获取锁对象
                RLock lock = redissonClient.getLock(lockKey);
                /*
                RedissonClient 加锁方式
                    第一种： lock.lock();
                    第二种:  lock.lock(10,TimeUnit.SECONDS);
                    第三种： lock.tryLock(100,10,TimeUnit.SECONDS);
                */

                // 尝试加锁
                boolean isSuccess = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);

                // 判断加锁是否成功
                if (isSuccess) {// true 上锁成功
                    try {
                        // 查询 DB
                        skuInfo = getSkuInfoDB(skuId); // 可能根本没有这个数据（会出现缓存穿透！！！）

                        if (skuInfo == null) {
                            // 设置空对象 防止缓存穿透 这个数据应该给一个【短暂】的过期时间！！！
                            SkuInfo skuInfo1 = new SkuInfo();

                            redisTemplate.opsForValue().set(skuKey, skuInfo1, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);

                            return skuInfo1;
                        }

                        // 将查询出来的数据放入缓存 商品详情页面数据应该有个较长的过期时间
                        redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);

                        // 返回查询到的真正的数据
                        return skuInfo;

                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        // 释放锁
                        lock.unlock();
                    }

                } else {// false 上锁失败
                    // 其他线程等待
                    Thread.sleep(1000);
                    return getSkuInfo(skuId);
                }

            } else {// 缓存中有数据：直接返回数据
                /**
                 * 一、 skuInfo 表示缓存中的数据，有以下两种情况
                 *    1 mysql数据库中 有 的数据skuId。比如：37、38
                 *    2 mysql数据库中 没有 的数据skuId。比如：138
                 *
                 *  如果缓存了 138，这个对象所有属性值是 null，那么页面渲染就是空白的。解决如下：
                 *
                 *      if(skuInfo.getId == null){
                 *          // 说明当前商品在数据库中不存在！
                 *      }
                 *      skuInfo;// 说明商品在数据库中存在！
                 *
                 *  从结果上说：如果没有特殊要求，只是对页面进行渲染的话，那么不用判断也可以！
                 *
                 *
                 *  二、注意：如果缓存了空对象 138，那这个缓存的过期时间需要设置为【短暂的】
                 *      如果有人在后台添加 138 这个商品：
                 *          如果在过期时间 外，那么没问题，用户能查询到新数据。
                 *          如果在过期时间 之内，那么查询的数据是缓存中的空对象；
                 *
                 *  三、对于操作缓存和数据库同步的时候！！！需要有一个原则【尽量保证数据库与缓存一致】
                 *      方案1 ：【同步操作】
                 *          在对数据库操作DML语言时候，insert、update、delete，先完成数据库操作，然后将缓存删除
                 *      方案2 ：【异步操作】
                 *          只操作数据库，可以使用消息队列来发送异步通知
                 */
                return skuInfo;
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 为了防止缓存宕机：从数据库中获取数据
        return getSkuInfoDB(skuId);
    }

    /**
     * 使用 Redis 的 Set命令 + Lua脚本实现 分布式锁
     *
     * @param skuId
     * @return
     */
    private SkuInfo getSkuInfoByRedisSetLua(Long skuId) {
        SkuInfo skuInfo = null;
        // 添加缓存部分！先查询缓存，如果缓存没有再查询数据库，并将数据放入缓存！！！
        // 缓存中是如何存储数据的？必须先确定数据类型
        //        if (true){
        //            // 缓存
        //        }else {
        //            // 查询DB --->>>放入缓存！！！
        //        }

        try {
            // 定义一个缓存的 key=sku:skuId:info
            String skuKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;

            // 获取缓存数据
            skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);

            // 判断缓存中的数据是否为空
            if (skuInfo == null) {
                // 为空，说明缓存中没有数据就：查询 DB --->>>放入缓存！！！
                // 从数据库获取数据时添加锁（防止缓存击穿！！！）

                // 设置锁 lockKey = sku:skuId:lock
                String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;

                // 设置 uuid
                String uuid = UUID.randomUUID().toString();

                // 执行上锁命令（防止缓存击穿！！！）
                Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);

                // 判断上锁是否成功
                if (flag) { // true 表示上锁成功
                    // 查询 DB
                    skuInfo = getSkuInfoDB(skuId); // 可能根本没有这个数据（会出现缓存穿透！！！）

                    if (skuInfo == null) {
                        // 设置空对象 防止缓存穿透 这个数据应该给一个【短暂】的过期时间！！！
                        SkuInfo skuInfo1 = new SkuInfo();

                        redisTemplate.opsForValue().set(skuKey, skuInfo1, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);

                        return skuInfo1;
                    }

                    // 将查询出来的数据放入缓存 商品详情页面数据应该有个较长的过期时间
                    redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);

                    // 获取数据完成之后：要删除对应的锁！！！使用 Lua脚本
                    // 定义 Lua 脚本：这个脚本只在客户端传入的值和键的口令串相匹配时，才对键进行删除。
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    // 将 Lua 脚本 放入 defaultRedisScript
                    DefaultRedisScript defaultRedisScript = new DefaultRedisScript();
                    defaultRedisScript.setScriptText(script);
                    // 设置一个返回值
                    // 因为删除判断的时候，返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型，那么返回字符串与0 会有发生错误。
                    defaultRedisScript.setResultType(Long.class);
                    // 执行 Lua 脚本 ===>>> 删除锁
                    // execute(RedisScript<T> script, List<K> keys, Object... args)
                    // 第一个要是 script 脚本 ，第二个需要判断的 key，第三个就是 key 所对应的值
                    redisTemplate.execute(defaultRedisScript, Arrays.asList(lockKey), uuid);

                    // 返回真正的数据
                    return skuInfo;

                } else { // 上锁失败的线程走 else 等待
                    try {
                        // 其他线程等待
                        Thread.sleep(1000);
                        // 继续获取数据
                        return getSkuInfo(skuId);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            } else { // 缓存中有数据了！！！
                // 从缓存中获取数据
                return skuInfo;
            }
        } catch (Exception e) {
            // 如果有日志系统 这个地方应该记录日志
            e.printStackTrace();
        }
        // 如果缓存宕机了，使用查询数据库兜底
        // 如果缓存对象宕机了，redisTemplate和获取数据的时候 都会出现异常!!!
        return getSkuInfoDB(skuId);
    }

    /**
     * 根据 skuId 查询 skuInfo 基本信息、图片列表集合(从数据库查询)
     *
     * @param skuId
     * @return
     */
    private SkuInfo getSkuInfoDB(Long skuId) {
        // 根据 skuId 查询 skuInfo 基本信息
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);

        if (skuInfo != null) {// 不为空才执行以下操作
            // 根据 skuId 查询图片列表集合
            QueryWrapper<SkuImage> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("sku_id", skuId);
            List<SkuImage> skuImageList = skuImageMapper.selectList(queryWrapper);

            // 为 skuInfo 设定 skuImageList
            skuInfo.setSkuImageList(skuImageList);
        }
        return skuInfo;// 为空返回 null
    }

    @GmallCache(prifix = "CategoryViewByCategory3Id:")
    @Override
    public BaseCategoryView getCategoryViewByCategory3Id(Long category3Id) {
        // BaseCategoryView视图里的 主键id 就是 category3Id
        return baseCategoryViewMapper.selectById(category3Id);
    }

    @GmallCache(prifix = "price:")
    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        if (null != skuInfo) {
            return skuInfo.getPrice();
        }
        return new BigDecimal("0");
    }

    @GmallCache(prifix = "spuSaleAttrListCheckBySku:")
    @Override
    public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
        // 调用 mapper 编写复杂 sql
        return spuSaleAttrMapper.selectSpuSaleAttrListCheckBySku(skuId, spuId);
    }

    @GmallCache(prifix = "skuValueIdsMap:")
    @Override
    public Map getSkuValueIdsMap(Long spuId) {
        // 定义一个 map 保存数据
        // key = 86|89，value = 32
        // map.put{"86|89"，"32"}
        Map<Object, Object> map = new HashMap<>();

        // 获取key、value所对应的数据，数据来自 sql语句查询的结果集
        List<Map> mapList = skuSaleAttrValueMapper.selectSaleAttrValuesBySpu(spuId);
        System.out.println("mapList===>>>" + mapList);

        if (!CollectionUtils.isEmpty(mapList)) {
            for (Map skuMap : mapList) {
                // 根据 sql 查询出来的结果 选择对应的字段作为 key 和 value
                map.put(skuMap.get("value_ids"), skuMap.get("sku_id"));
            }
        }
        System.out.println("map===>>>" + map);
        return map;
    }

    @GmallCache(prifix = "index:")
    @Override
    public List<JSONObject> getBaseCategoryList() {
        // 声明一个 Json 对象的集合
        List<JSONObject> list = new ArrayList<>();

        // 从视图 BaseCategoryView 获取所有分类数据集合
        List<BaseCategoryView> baseCategoryViewsList = baseCategoryViewMapper.selectList(null);

        // 按照 一级分类id 进行分组：collect(Collectors.groupingBy(BaseCategoryView::getCategory1Id)
        // key = 一级分类id，value = List<BaseCategoryView>
        Map<Long, List<BaseCategoryView>> category1Map = baseCategoryViewsList.stream().collect(Collectors.groupingBy(BaseCategoryView::getCategory1Id));

        // 定义确定一级分类个数的变量 初始值为1
        int index = 1;

        // 获取一级分类下所有数据。数据都在 category1Map 的 value 里
        for (Map.Entry<Long, List<BaseCategoryView>> entry1 : category1Map.entrySet()) {
            // 获取一级分类Id
            Long category1Id = entry1.getKey();

            // 获取一级分类下面的所有集合(因为有二级分类数据 故取名 category2List )
            List<BaseCategoryView> category2List = entry1.getValue();

            // 获取一级分类名称 categoryName (因为进行分组了：同一个分类下所有名称一样 故从集合中取第一个即可)
            String category1Name = category2List.get(0).getCategory1Name();

            // 声明一个 JSONObject 存储 一级分类数据
            JSONObject category1 = new JSONObject();
            category1.put("index", index);// 注意json格式
            category1.put("categoryId", category1Id);
            category1.put("categoryName", category1Name);
            // 一级分类下还有二级分类集合
            // category1.put("categoryChild", category2List);

            // 变量迭代，更新一级分类个数
            index++;

            // 按照 二级分类id 进行分组
            // key = 二级分类id，value =  List<BaseCategoryView>
            Map<Long, List<BaseCategoryView>> category2Map = category2List.stream().collect(Collectors.groupingBy(BaseCategoryView::getCategory2Id));

            // 声明二级分类对象集合 category2Child ：存储同一个一级分类下所有的二级分类数据
            List<JSONObject> category2Child = new ArrayList<>();

            // 循环遍历 category2Map 获取二级分类数据
            for (Map.Entry<Long, List<BaseCategoryView>> entry2 : category2Map.entrySet()) {
                // 获取二级分类Id
                Long category2Id = entry2.getKey();

                // 获取二级分类下面的所有集合(因为有三级分类数据 故取名 category3List )
                List<BaseCategoryView> category3List = entry2.getValue();

                // 获取二级分类名称 categoryName
                // 因为按二级分类id进行分组了 所以能通过get(0)取出同一组的相同名称
                String category2Name = category3List.get(0).getCategory2Name();

                // 声明一个 JSONObject 存储 二级分类数据
                JSONObject category2 = new JSONObject();
                category2.put("categoryId", category2Id);// 注意 json 格式
                category2.put("categoryName", category2Name);
                // 二级分类下还有三级分类集合
                //category2.put("categoryChild",category3List);

                // 将每次循环的二级分类数据 放入 category2Child 集合
                category2Child.add(category2);

                // 声明三级分类对象集合 category3Child ：存储同一个二级分类下所有的三级分类数据
                List<JSONObject> category3Child = new ArrayList<>();

                // 循环三级分类数据（三级分类数据无重复的 不进行分组，直接遍历）
                category3List.stream().forEach((category3View) -> {

                    // 声明一个 JSONObject 存储 三级分类数据
                    JSONObject category3 = new JSONObject();

                    // 获取三级分类id 并保存
                    category3.put("categoryId", category3View.getCategory3Id());

                    // 获取三级分类的名称 并保存
                    category3.put("categoryName", category3View.getCategory3Name());

                    // 将每次循环的三级分类数据 放入 category3Child 集合
                    category3Child.add(category3);

                });
                // 将三级数据集合放入 二级分类Json对象
                category2.put("categoryChild", category3Child);
            }
            // 将二级数据集合放入 一级分类Json对象
            category1.put("categoryChild", category2Child);

            // 将所有的 一级分类数据(一级分类Json对象) 放入到 List<JSONObject> list 集合中
            list.add(category1);
        }
        // 返回 Json 对象集合
        return list;
    }

    @Override
    public BaseTrademark getTrademarkByTmId(Long tmId) {
        BaseTrademark baseTrademark = baseTrademarkMapper.selectById(tmId);
        return baseTrademark;
    }

    @Override
    public List<BaseAttrInfo> getAttrList(Long skuId) {
        // 复杂 sql  使用 xml 来编写
        return baseAttrInfoMapper.selectBaseAttrInfoListBySkuId(skuId);
    }
}
